前言
2014 年 Google I/O 推出了 Material Design 的设计规范,Android 也顺应推出了相关的 Support Design 支持库。库中包含 AppBarLayout,FloatingActionButton 等符合设计规范的控件,但其中最重要的是一个 CoordinatorLayout 的容器,最大的作用就是为子 View 间交互提供便利,我们可以在各大app上(知乎,uc等看到它的身影)
引入
gradle配置
1 | dependencies { |
简介
CoordinatorLayout 官方介绍(需要科学上网)
CoordinatorLayout is a super-powered FrameLayout.
CoordinatorLayout is intended for two primary use cases:1. As a top-level application decor or chrome layout 2. As a container for a specific interaction with one or more child views
CoordinatorLayout 可以作为一个布局文件的根部局,方便子 View 间的相互作用。CoordinatorLayout 的继承结构如下:
1 | public class CoordinatorLayout |
CoordinatorLayout 只实现了 NestedScrollingParent 接口,而没有 NestedScrollingChild 接口,那么
- 这个布局控件不适用于作为一个子 View 做嵌套布局
- 5.0 以下版本的 ScrollView 和 ListView 因为没有实现 NestedScrollingChild 接口而无法在 CoordinatorLayout 布局上运用,推荐使用 RecyclerView 和 NestedScrollView
而 CoordinatorLayout 实现子 View 间交互的最大功臣就是 Behavior
—— 一个 CoordinatorLayout 的内部类
Behavior简介
CoordinatorLayout.Behavior 官方介绍(需要科学上网)
该类比较简单,只是定义一些方法,需要时我们可以自定义 behavior 类继承该类,通过复写相关方法来完成子 View 的代理
- View 的绘制
- onMeasureChild
- onLayoutChild
- View 的事件分发
- onInterceptTouchEvent
- onTouchEvent
- child 的嵌套滑动响应
- onStartNestedScroll
- onNestedPreScroll
- onNestedScroll
- onStopNestedScroll
- …
- child 间的依赖
- layoutDependsOn
- onDependentViewChanged
在 Support Design 库中有以下几个类 AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior, BottomSheetBehavior
CoordinatorLayout 和 Behavior 配合
那他们之间是如何协同工作的呢?
在 CoordinatorLayout 的自定义 LayoutParams 中有 Behavior 变量对象,在它的构造函数中有获取 behavior 实例的相关代码
1 | LayoutParams(Context context, AttributeSet attrs) { |
parseBehavior 方法通过布局文件中设置的相关字符串解析得到实例,值得一提的是只有 CoordinatorLayout 的子 View 设置了 behavior 才有用,CoordinatorLayout 就可以在合适的时机通过这些设置在 lp 中的 behavior 来管理对应的子 View
比如说:
1 |
|
只有当子 View 对应的 behavior 的 onLayoutChild 方法返回 false 的时候才会调用父 View 的方法。由此可见,behavior 的优先级是高于父类方法的,Behaivor 的触摸分发,绘制类似,具体代码不在此展示,有兴趣可以直接看源码
- 滑动处理:CoordinatorLayout 继承了 NestedScrollingParent,而 behavior 代理实现 NestedScrollingChild 的相关方法实现嵌套滑动,当子View在处理滑动事件之前,先告诉自己的父 View 是否需要先处理这次滑动事件,父 View 处理完之后,告诉子 View 它处理的多少滑动距离,剩下的还是交给子 View 自己来处理,当然具体逻辑也是比较复杂的
- 子 View 间的交互代理:主要用于监听同级别子 view 的大小、位置、显示状态的改变,通过一下几个方法实现的
1 | /** |
在自定义的 behavior 中,layoutDependsOn 方法可以定义索要依赖的同级控件类型,比如我们需要依赖 Button,那么就需要return dependency instanceof Button;
而一般而言如何响应从属布局的变化,则需要我们在 onDependentViewChanged 中书写具体的逻辑。视线转回 CoordinatorLayout 中, onChildViewsChanged 方法中会有这 behavior 的三个方法相关调用,具体代码如下
1 | final void onChildViewsChanged(@DispatchChangeEvent final int type) { |
值得注意的是该方法的调用是在 view 进行绘制之前
1 | class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener { |
而在 onNestedScroll,onNestedPreScroll,onNestedFling 方法中也会调用到 onChildViewsChanged(EVENT_NESTED_SCROLL) 方法
,所以在该方法中也会进行两个状态冲突的处理
值得注意的是两个 view 之间并不能相互依赖 mDependencySortedChildren 是类中确定依赖关系顺序的变量,在 prepareChildren 方法中,系统会按照依赖关系对所有的子 View 进行排序。因为使用的是有向无环图,所以不会产生相互依赖的情况产生,同时这个变量会影响测量和绘制的顺序。’
1 | private void prepareChildren() { |
这个方法会在 onMeasure 中调用,所以在一开始 CoordinatorLayout 就确定由依赖关系确定子 view 的调用关系
behavior 的简单使用
behavior 定义在 CoordinatorLayout 子 view 的布局文件中,用app:layout_behavior
命名空间修饰,传入定义好的 behavior 全路径名即可,当然你可也可以自定义 Behavior